namespace OasisCiq.Xml
{
    // ReSharper disable MemberCanBePrivate.Global

    using System;
    using System.Collections.Generic;
    using System.Xml;
    using CommonTypes;
    using Xnl;

    public class XnlXmlReader
    {
        private const string XmlNS = XmlNamespaces.Name;
        private readonly XmlReader reader;
        private readonly CommonTypesXmlReader ctReader = new CommonTypesXmlReader();
        private readonly XLinkXmlReader xLinkReader = new XLinkXmlReader();

        public XnlXmlReader(XmlReader reader)
        {
            this.reader = reader;
        }

        public NameLine ReadNameLine()
        {
            if (!CheckElement("NameLine"))
            {
                return null;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            var value = reader.ReadElementContentAsString();
            var element = new NameLine(value);
            attrs = ctReader.ReadAbbreviationGroup(element, attrs);
            attrs = ctReader.ReadDataQualityGroup(element, attrs);
            attrs = ReadNameLineAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            return element;
        }

        public OrganisationNameType ReadOrganisationName()
        {
            return ReadOrganisationName("OrganisationName", XmlNS);
        }

        public OrganisationNameType ReadOrganisationName(string elementName, string elementNamespace)
        {
            var element = new OrganisationNameType();
            return ReadOrganisationName(element, elementName, elementNamespace) ? element : null;
        }

        public bool ReadOrganisationName(
            OrganisationNameType element, 
            string elementName, 
            string elementNamespace)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            if (!CheckElement(elementName, elementNamespace))
            {
                return false;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            attrs = ctReader.ReadDataQualityGroup(element, attrs);
            attrs = ctReader.ReadValidityDateGroup(element, attrs);
            attrs = ctReader.ReadLanguageCodeGroup(element, attrs);
            attrs = xLinkReader.Read(element, attrs);
            attrs = ReadOrganisationNameAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            if (!reader.IsEmptyElement)
            {
                reader.Read(); // to n:NameElement or n:SubDivisionName

                while (true)
                {
                    var nameElement = ReadOrganisationNameElement();
                    if (nameElement == null)
                    {
                        break;
                    }

                    if (element.NameElements == null)
                    {
                        element.NameElements = new List<OrganisationNameElement>();
                    }

                    element.NameElements.Add(nameElement);
                }

                while (true)
                {
                    var nameElement = ReadSubDivisionName();
                    if (nameElement == null)
                    {
                        break;
                    }

                    if (element.SubDivisions == null)
                    {
                        element.SubDivisions = new List<SubDivisionName>();
                    }

                    element.SubDivisions.Add(nameElement);
                }
            }

            reader.Read(); // from n:OrganisationName empty or end element to next node
            return true;
        }

        public PersonNameType ReadPersonName()
        {
            var element = new PersonNameType();
            return ReadPersonName(element, "PersonName", XmlNS) ? element : null;
        }

        public bool ReadPersonName(PersonNameType element, string elementName, string elementNamespace)
        {
            if (element == null)
            {
                throw new ArgumentNullException();
            }

            if (!CheckElement(elementName, elementNamespace))
            {
                return false;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            attrs = ctReader.ReadDataQualityGroup(element, attrs);
            attrs = ctReader.ReadValidityDateGroup(element, attrs);
            attrs = ctReader.ReadLanguageCodeGroup(element, attrs);
            attrs = xLinkReader.Read(element, attrs);
            attrs = ReadPersonNameAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            if (!reader.IsEmptyElement)
            {
                reader.Read(); // to n:NameElement
                while (true)
                {
                    var nameElement = ReadPersonNameElement();
                    if (nameElement == null)
                    {
                        break;
                    }

                    if (element.NameElements == null)
                    {
                        element.NameElements = new List<PersonNameElement>();
                    }

                    element.NameElements.Add(nameElement);
                }
            }

            reader.Read(); // from n:PersonName empty or end element to next node
            return true;
        }

        public PartyNameType ReadPartyName()
        {
            return ReadPartyName("PartyName", XmlNS);
        }

        public PartyNameType ReadPartyName(string elementName, string elementNamespace)
        {
            var element = new PartyNameType();
            return ReadPartyName(element, elementName, elementNamespace) ? element : null;
        }

        public bool ReadPartyName(PartyNameType element, string elementName, string elementNamespace)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            if (!CheckElement(elementName, elementNamespace))
            {
                return false;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            attrs = ReadPartyNameAttributesWhithoutExtendedAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            ReadPartyNameChildren(element);
            reader.Read(); // from n:PartyName empty or end element to next node
            return true;
        }

        public IEnumerable<_XmlAttribute> ReadPartyNameAttributesWhithoutExtendedAttributes(
            PartyNameType element,
            IEnumerable<_XmlAttribute> attributes)
        {
            attributes = ctReader.ReadDataQualityGroup(element, attributes);
            attributes = ctReader.ReadValidityDateGroup(element, attributes);
            attributes = ctReader.ReadLanguageCodeGroup(element, attributes);
            attributes = xLinkReader.Read(element, attributes);
            attributes = ReadPartyNameAttributes(element, attributes);
            return attributes;
        }

        public void ReadPartyNameChildren(PartyNameType element)
        {
            if (reader.IsEmptyElement)
            {
                return;
            }

            reader.Read(); // to n:NameLine or n:PersonName or n:OrganisationName
            while (true)
            {
                var nameLine = ReadNameLine();
                if (nameLine == null)
                {
                    break;
                }

                if (element.NameLines == null)
                {
                    element.NameLines = new List<NameLine>();
                }

                element.NameLines.Add(nameLine);
            }

            while (true)
            {
                var personName = ReadPersonName();
                if (personName == null)
                {
                    break;
                }

                if (element.PersonNames == null)
                {
                    element.PersonNames = new List<PersonNameType>();
                }

                element.PersonNames.Add(personName);
            }

            while (true)
            {
                var organisationName = ReadOrganisationName();
                if (organisationName == null)
                {
                    break;
                }

                if (element.OrganisationNames == null)
                {
                    element.OrganisationNames = new List<OrganisationNameType>();
                }

                element.OrganisationNames.Add(organisationName);
            }
        }

        public OrganisationNameElement ReadOrganisationNameElement()
        {
            if (!CheckElement("NameElement"))
            {
                return null;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            var value = reader.ReadElementContentAsString();
            var element = new OrganisationNameElement(value);
            attrs = ctReader.ReadAbbreviationGroup(element, attrs);
            attrs = ReadOrganisationNameElementAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            return element;
        }

        public SubDivisionName ReadSubDivisionName()
        {
            if (!CheckElement("SubDivisionName"))
            {
                return null;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            var value = reader.ReadElementContentAsString();
            var element = new SubDivisionName(value);
            attrs = ctReader.ReadAbbreviationGroup(element, attrs);
            attrs = ReadSubDivisionNameAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            return element;
        }

        public PersonNameElement ReadPersonNameElement()
        {
            if (!CheckElement("NameElement"))
            {
                return null;
            }

            var attrs = _XmlAttribute.GetAttributes(reader);
            var value = reader.ReadElementContentAsString();
            var element = new PersonNameElement(value);
            attrs = ctReader.ReadAbbreviationGroup(element, attrs);
            attrs = ReadPersonNameElementAttributes(element, attrs);
            ctReader.ReadExtendedAttributes(element, attrs);
            return element;
        }

        public IEnumerable<_XmlAttribute> ReadNameLineAttributes(
            NameLine element, 
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "Type":
                            element.Type = new NameLineTypeList(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

        public IEnumerable<_XmlAttribute> ReadOrganisationNameAttributes(
            OrganisationNameType element,
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "Type":
                            element.Type = new OrganisationNameTypeList(xmlAttribute.Value);
                            continue;
                        case "OrganisationID":
                            element.OrganisationId = new CTString(xmlAttribute.Value);
                            continue;
                        case "OrganisationIDType":
                            element.OrganisationIdType = new OrganisationIdTypeList(xmlAttribute.Value);
                            continue;
                        case "ID":
                            element.Id = new CTString(xmlAttribute.Value);
                            continue;
                        case "Usage":
                            element.Usage = new OrganisationNameUsageList(xmlAttribute.Value);
                            continue;
                        case "Status":
                            element.Status = new StatusList(xmlAttribute.Value);
                            continue;
                        case "NameKey":
                            if (element.NameKey == null)
                            {
                                element.NameKey = new NameKeyGroup();
                            }

                            element.NameKey.NameKey = new CTString(xmlAttribute.Value);
                            continue;
                        case "NameKeyRef":
                            if (element.NameKey == null)
                            {
                                element.NameKey = new NameKeyGroup();
                            }

                            element.NameKey.NameKeyRef = new CTString(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

        public IEnumerable<_XmlAttribute> ReadPersonNameAttributes(
            PersonNameType element,
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "Type":
                            element.Type = new PersonNameTypeList(xmlAttribute.Value);
                            continue;
                        case "PersonID":
                            element.PersonId = new CTString(xmlAttribute.Value);
                            continue;
                        case "PersonIDType":
                            element.PersonIdType = new PersonIdTypeList(xmlAttribute.Value);
                            continue;
                        case "ID":
                            element.Id = new CTString(xmlAttribute.Value);
                            continue;
                        case "Usage":
                            element.Usage = new PersonNameUsageList(xmlAttribute.Value);
                            continue;
                        case "Status":
                            element.Status = new StatusList(xmlAttribute.Value);
                            continue;
                        case "NameKey":
                            if (element.NameKey == null)
                            {
                                element.NameKey = new NameKeyGroup();
                            }

                            element.NameKey.NameKey = new CTString(xmlAttribute.Value);
                            continue;
                        case "NameKeyRef":
                            if (element.NameKey == null)
                            {
                                element.NameKey = new NameKeyGroup();
                            }

                            element.NameKey.NameKeyRef = new CTString(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

        public IEnumerable<_XmlAttribute> ReadPartyNameAttributes(
            PartyNameType element,
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "PartyNameID":
                            element.PartyNameId = new CTString(xmlAttribute.Value);
                            continue;
                        case "PartyNameIDType":
                            element.PartyNameIdType = new PartyNameIdTypeList(xmlAttribute.Value);
                            continue;
                        case "ID":
                            element.Id = new CTString(xmlAttribute.Value);
                            continue;
                        case "Usage":
                            element.Usage = new PartyNameUsageList(xmlAttribute.Value);
                            continue;
                        case "JointNameConnector":
                            element.JointNameConnector = new JointNameConnectorList(xmlAttribute.Value);
                            continue;
                        case "Status":
                            element.Status = new StatusList(xmlAttribute.Value);
                            continue;
                        case "NameKey":
                            if (element.NameKey == null)
                            {
                                element.NameKey = new NameKeyGroup();
                            }

                            element.NameKey.NameKey = new CTString(xmlAttribute.Value);
                            continue;
                        case "NameKeyRef":
                            if (element.NameKey == null)
                            {
                                element.NameKey = new NameKeyGroup();
                            }

                            element.NameKey.NameKeyRef = new CTString(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

        public IEnumerable<_XmlAttribute> ReadOrganisationNameElementAttributes(
            OrganisationNameElement element,
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "ElementType":
                            element.ElementType = new OrganisationNameElementList(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

        public IEnumerable<_XmlAttribute> ReadPersonNameElementAttributes(
            PersonNameElement element,
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "ElementType":
                            element.ElementType = new PersonNameElementList(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

        public IEnumerable<_XmlAttribute> ReadSubDivisionNameAttributes(
            SubDivisionName element,
            IEnumerable<_XmlAttribute> attributes)
        {
            foreach (var xmlAttribute in attributes)
            {
                if (xmlAttribute.NS == XmlNS)
                {
                    switch (xmlAttribute.Name)
                    {
                        case "Type":
                            element.Type = new SubDivisionTypeList(xmlAttribute.Value);
                            continue;
                    }
                }

                yield return xmlAttribute;
            }
        }

/*
        private bool SearchElement(string name)
        {
            do
            {
                if (CheckElement(name))
                {
                    return true;
                }
            }
            while (reader.Read());

            return false;
        }
*/

        public bool CheckElement(string name)
        {
            return CheckElement(name, XmlNS);
        }

        public bool CheckElement(string name, string ns)
        {
            var elem = reader.NodeType == XmlNodeType.Element ? reader.LocalName : null;
            return elem != null && reader.NamespaceURI == ns && elem == name;
        }
    }
}